package otp;

import java.awt.*;
import java.awt.event.*;

import javax.swing.*;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;

import util.Crypto;

public class TOTPApplication {

	public static void main(String[] args) {
		new TOTPApplication();
	}

	public final static String APPLICATION_TITLE 				= "TOTP Application";
	public final static String SERIAL_BUTTON_SERIALSHOWN_TRUE	= "Hide the Serial";
	public final static String SERIAL_BUTTON_SERIALSHOWN_FALSE 	= "Show the Serial";
	public final static String START_GENERATING_BUTTON 			= "Start Generating";
	public final static String STOP_GENERATING_BUTTON 			= "Stop Generating";
	public final static String TIMER_LABEL 						= "Time left";
	public final static String INCORRECT_SERIAL_STRING 			= "Incorrect Serial";

	public final static long SECONDS_PASSWORD_ALIVE				= util.Timer.MINUTE_HALF;

	private JFrame frame 					= new JFrame(APPLICATION_TITLE);
	private JButton buttonShowSerial 		= new JButton(SERIAL_BUTTON_SERIALSHOWN_TRUE);
	private JButton buttonGenerating 		= new JButton(START_GENERATING_BUTTON);
	private JLabel labelTimer 				= new JLabel(TIMER_LABEL);
	private JTextField textFieldPassword 	= new JTextField();
	private JTextField textFieldSerial 		= new JTextField();
	private JTextField textFieldTimer 		= new JTextField();
	private JPanel panel 					= new JPanel();

	private String serial = "";
	private volatile boolean isGenerating = false;
	private boolean serialIsShown = true;

	public TOTPApplication() {
		// The main window
		frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

		// Layoutmanager setup
		GroupLayout layout = new GroupLayout(panel);
		panel.setLayout(layout);
		layout.setAutoCreateGaps(true);
		layout.setAutoCreateContainerGaps(true);

		layout.setHorizontalGroup( layout.createSequentialGroup()
				.addGroup(layout.createParallelGroup(GroupLayout.Alignment.LEADING, true)
						.addComponent(buttonShowSerial, 200, 200, 200)
						.addComponent(buttonGenerating, 200, 200, 200)
						.addComponent(labelTimer, 200, 200, 200)
						).addGroup(layout.createParallelGroup(GroupLayout.Alignment.TRAILING, true)
								.addComponent(textFieldSerial, 200, 200, 200)
								.addComponent(textFieldPassword, 200, 200, 200)
								.addComponent(textFieldTimer, 200, 200, 200)

								)
				);
		layout.setVerticalGroup( layout.createSequentialGroup()
				.addGroup(layout.createParallelGroup(GroupLayout.Alignment.BASELINE, true)
						.addComponent(buttonShowSerial, 50, 50, 50)
						.addComponent(textFieldSerial, 50, 50, 50)
						).addGroup(layout.createParallelGroup(GroupLayout.Alignment.BASELINE, true)
								.addComponent(buttonGenerating, 50, 50, 50)
								.addComponent(textFieldPassword, 50, 50, 50)
								).addGroup(layout.createParallelGroup(GroupLayout.Alignment.BASELINE, true)
										.addComponent(labelTimer, 50, 50, 50)
										.addComponent(textFieldTimer, 50, 50, 50)
										)
				);

		// Actionlistener setup
		textFieldSerial.getDocument().addDocumentListener(new SerialTextFieldListener());
		buttonShowSerial.addActionListener(new ShowSerialAction());
		buttonGenerating.addActionListener(new GeneratePasswordAction());

		// Initial setup what components the user can edit or not
		textFieldTimer.setEnabled(false);
		textFieldPassword.setEditable(false);

		// Look and feel
		textFieldTimer.setFont(new Font(Font.SANS_SERIF, Font.PLAIN, 22));
		textFieldTimer.setHorizontalAlignment(JTextField.CENTER);
		textFieldPassword.setFont(new Font(Font.SANS_SERIF, Font.PLAIN, 22));
		textFieldPassword.setHorizontalAlignment(JTextField.CENTER);
		textFieldSerial.setFont(new Font(Font.SANS_SERIF, Font.PLAIN, 22));
		textFieldSerial.setHorizontalAlignment(JTextField.CENTER);
		labelTimer.setHorizontalAlignment(JLabel.CENTER);

		// Round up last GUI settings
		frame.setContentPane(panel);
		frame.pack();

		// Set frame at center of screen and show
		Dimension dimension = Toolkit.getDefaultToolkit().getScreenSize();
		frame.setLocation((dimension.width / 2) - (frame.getSize().width / 2), dimension.height / 2 - frame.getSize().height / 2);
		frame.setVisible(true);
	}

	// The action listener for the show serial button.
	private class ShowSerialAction implements ActionListener {
		public void actionPerformed(ActionEvent e) {
			if(serialIsShown) {
				serialIsShown = false;
				buttonShowSerial.setText(TOTPApplication.SERIAL_BUTTON_SERIALSHOWN_FALSE);
				textFieldSerial.setText("");
				textFieldSerial.setEnabled(false);
			} else {
				serialIsShown = true;
				buttonShowSerial.setText(TOTPApplication.SERIAL_BUTTON_SERIALSHOWN_TRUE);
				textFieldSerial.setText(serial);
				if(!isGenerating) {
					textFieldSerial.setEnabled(true);
				}
			}
		}
	}

	// The document listener for the serial text field. This is so that every change in the serial field,
	// causes the "serial" variable in this object to be updated automatically, but only if the serial is shown.
	private class SerialTextFieldListener implements DocumentListener {
		public void changedUpdate(DocumentEvent e) {
			if(serialIsShown) {
				serial = textFieldSerial.getText();
			}

		}
		public void insertUpdate(DocumentEvent e) {
			if(serialIsShown) {
				serial = textFieldSerial.getText();
			}

		}
		public void removeUpdate(DocumentEvent e) {
			if(serialIsShown) {
				serial = textFieldSerial.getText();
			}
		}
	}

	// The action listener for the generate password button.
	// Starts a TimerThread to handle generating a password every 30 seconds,
	// stops it by setting the "isGenerating" variable to false.
	private class GeneratePasswordAction implements ActionListener {
		public void actionPerformed(ActionEvent e) {
			if(serial.length() == 0) {
				textFieldPassword.setText(INCORRECT_SERIAL_STRING);
			} else {			
				try {
					if(!isGenerating) {
						isGenerating = true;
						buttonGenerating.setText(STOP_GENERATING_BUTTON);
						// The user is not allowed to edit the serial during password generation
						if(serialIsShown) {
							textFieldSerial.setEnabled(false);
						}
						new Thread(new TimerThread()).start();
					} else {
						// Let the thread clean up the UI, such as resetting the buttons/textfields
						// we don't want to start a new thread until the previous one is done anyway
						isGenerating = false;
					}
				} catch (NumberFormatException error) {
					textFieldPassword.setText(INCORRECT_SERIAL_STRING);
				}  catch (IllegalArgumentException error) {
					textFieldPassword.setText(INCORRECT_SERIAL_STRING);
				} catch (Exception error) {
					error.printStackTrace();
				}
			}
		}
	}

	// Thread to generate the code, this also updates the expiration time.
	private class TimerThread implements Runnable {
		public void run() {
			try {
				while(isGenerating) {
					long lastMoment = util.Timer.getLastMomentInMinute(SECONDS_PASSWORD_ALIVE);

					String passWord = Crypto.generateTOTP(serial, Long.toHexString(lastMoment));
					textFieldPassword.setText(passWord);

					textFieldTimer.setText(String.valueOf(util.Timer.getTimeLeftUntillNextMoment(SECONDS_PASSWORD_ALIVE)));
					Thread.sleep(250);
				}
			} catch (InterruptedException e) {
				e.printStackTrace();
			} catch (Exception e) {
				e.printStackTrace();
			}
			
			// Reset the text fields
			textFieldTimer.setText("");
			textFieldPassword.setText("");
			buttonGenerating.setText(START_GENERATING_BUTTON);
			
			// Password generation has stopped, user can edit the serial again
			if(serialIsShown) {
				textFieldSerial.setEnabled(true);
			}
		}
	}
}